#!/usr/bin/env python

#
# This file is part of GMultiserver
# Copyright (C) 2012 Gustaw Lewandowski 
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

from imaplib import CRLF
import email, os
from time import time
from shutil import rmtree
from utils import Logged, config

def convert_array_to_str(a):
	# collapseNestedList do the same
	return str(a).replace('[','(').replace(']',')').replace('None', 'NIL').replace(',','').replace("'",'"')

def get_section(sec, m):
	sec = sec.split('.')
	p = None
	for i in sec:
		d = 0
		try:
			i = int(i)
		except ValueError:
			return None
		for p in m.walk():
			if d == i:
				if p.get_content_maintype() == 'multipart':
					m = p
				break
			d += 1
	return p

def get_header(m):
	s = ''
	for i in m.items():
		if i[0] != 'Content-Type':
			s += str(i[0]) + ': ' + str(i[1]) + CRLF
	return s[:-2]

def convert_to_binary(m):
	for i in m.walk():
		if i.is_multipart():
			for j in i.get_payload():
				convert_to_binary(j)
		else:
			if i.get('Content-Transfer-Encoding') == 'base64':
				i.set_payload(i.get_payload().decode('base64'))
				i.replace_header('Content-Transfer-Encoding', '8bit')

def convert_to_log(m):
	if m.is_multipart():
		for j in m.get_payload():
			convert_to_log(j)
	else:
		if m.get('Content-Transfer-Encoding') is None:
			if m.get('Content-Type') is not None and m.get('Content-Type')[:4] == 'text':
				return
			m.set_payload(repr(m.get_payload()[:15] + ' ...'))
			return
		if m.get('Content-Transfer-Encoding').lower() == 'base64':
			b64 = ''
			try:
				b64 = m.get_payload().decode('base64')
			except Exception, e:
				# TODO: move it GMessage
				print e
				return
			if m.get('Content-Type')[:4] == 'text':
				m.set_payload(m.get_payload().decode('base64'))
			else:
				m.set_payload(repr(m.get_payload().decode('base64')[:15] + '...'))
		elif m.get('Content-Transfer-Encoding').lower() == 'binary' or m.get('Content-Transfer-Encoding').lower() == '8bit':
			m.set_payload(repr(m.get_payload()[:15] + ' ...'))

def convert_to_b64(m):
	for i in m.walk():
		if i.is_multipart():
			for j in i.get_payload():
				convert_to_b64(j)
		else:
			if i.get('Content-Transfer-Encoding') == '8bit':
				i.set_payload(i.get_payload().encode('base64'))
				i.replace_header('Content-Transfer-Encoding', 'base64')

def get_param(m, param, format = True):
	items = m.items()
	d = {}
	for i in items:
		d[i[0].upper()] = i[1]
	if param.upper() in d:
		if format:
			return '"%s"' % d[param.upper()]
		return d[param.upper()]
	return 'NIL'

def get_param_addr(m, param):
	items = m.items()
	d = {}
	for i in items:
		d[i[0].upper()] = i[1]
	if param.upper() in d:
		a = d[param.upper()]
		if a[-1] == '>': # friendly name <nick@host> or only <nick@host>
			ff = a.find('<')
			friendly_name = a[:ff - 1]
			if ff == 0: friendly_name = 'NIL' # no friendly name
			nick, host = a[ff+1:-1].split('@')
			return '((%s NIL "%s" "%s"))' % (friendly_name, nick, host) 
		a = a.split('@') # no <>
		return '((NIL NIL "%s" "%s"))' % (a[0], a[1]) 
	return 'NIL'

def get_envelope(m):
	return '(%s %s %s %s %s %s %s %s %s %s)' % (get_param(m, 'date'), get_param(m, 'subject'), \
					   get_param_addr(m, 'from'), get_param_addr(m, 'sender'), get_param_addr(m, 'reply-to'), \
					   get_param_addr(m, 'to'), get_param_addr(m, 'cc'), get_param_addr(m, 'bcc'), \
					   get_param_addr(m, 'in-reply-to'), get_param(m, 'message-id'))

def get_bodystructure_old(m):
	s = '('
	for i in m.walk():
		if i.is_multipart():
			for j in i.get_payload():
				n_type = j.get_content_maintype()
				if n_type != 'multipart':
					s += get_bodystructure_old(j)
		else:
			main_type = m.get_content_maintype()
			if main_type != 'multipart':
				s += '"%s" "%s"' % (main_type, m.get_content_subtype())
			else:
				s += ' "mixed"'
			content_parm = m.get_params()
			ss = ''
			found_content_par = False
			for k in content_parm:
				if k[0] != '' and k[1] != '':
					found_content_par = True
					ss += '"%s" {%d}\r\n"%s" ' % (k[0], len(k[1])+2, k[1])
			if found_content_par:
				ss = ss[:-1]
			if len(ss) != 0:
				s += ' (%s)' % ss
			else:
				s += ' NIL'
			if main_type == 'multipart':
				return s + ' NIL NIL)'
			s += ' %s %s %s' % (get_param(m , 'content-id'), get_param(m, 'content-description'),\
							get_param(m , 'content-transfer-encoding'))
			ss = get_param(m, 'content-length')
			if ss == 'NIL':
				ss = str(len(m.as_string()))
			s += ' ' + ss
			if main_type == 'text':
				#s += ' ' + str(len(m.get_payload().split('\n')))
				s += ' 0'

			if main_type == 'message' or main_type == 'rfc822':
				pass
				# todo add fist envelope and bodystuct
			#s += ' ' + get_param(m, 'content-lines')
			s += ' NIL'
			file_name = m.get_filename(None)
			if file_name != None:
				s += ' ("INLINE" ("FILENAME" "%s")) NIL' % file_name
			else:
				s += ' ("inline" NIL) NIL'
	return s + ')'

def cdls(m, br = False):
	ct = get_param(m, 'content-disposition')
	if ct == 'NIL': return '("inline" NIL)'
	ctl = ct[:-1].split(';')
	s = '(%s" ' % ctl[0]
	if len(ctl) == 1:
		s += 'NILL' # last L will be truncated 
	else:
		for c1 in ctl[1:]:
			c2 = c1.split('=', 1)
			if len(c2) == 1:
				s += 'NILL'
				continue
			if br:
				s += '("%s" {%i}\n"%s") ' % (c2[0][1:], len(c2[1]), c2[1])
			else:
				s += '("%s" "%s") ' % (c2[0][1:], c2[1])
	s = s.replace('""','"')
	return s[:-1] + ')'

def ctls(m):
	s = '('
	ct = get_param(m, 'content-type')[:-1]
	ctl = ct.split(';')[1:]
	if len(ctl) == 0:
		return 'NIL'
	for c1 in ctl:
		c2 = c1.split('=', 1)
		s += '"%s" {%i}\n%s ' % (c2[0][1:], len(c2[1]), c2[1])
	return s[:-1] + ')'

def newbds(m):
	s = ''
	mt = m.get_content_maintype()
	if m.is_multipart() and m.get_content_maintype() != 'message':
		for subpart in m.get_payload():
			s += newbds(subpart)
		return '%s "%s" %s %s NIL)' % (s, m.get_content_subtype(), ctls(m), cdls(m))
	s += '("%s" "%s" %s %s %s %s' % (m.get_content_maintype(), m.get_content_subtype(), \
		ctls(m), get_param(m, 'content-id'), get_param(m, 'content-description'),\
		get_param(m, 'content-transfer-encoding'))
	ss = get_param(m, 'content-length', False)
	if ss == 'NIL':
		ss = str(len(m.as_string()))
	s += ' ' + ss
	if mt == 'message':
		if len(m.get_payload()) == 0:
			s += ' NIL NIL'
		else:
			mm = m.get_payload()[0]
			s += ' %s %s' % (get_envelope(mm), newbds(mm))
	if mt == 'message' or mt == 'text':
		ss = get_param(m, 'content-lines')
		if ss == 'NIL':
			s += ' 0'
		else:
			s += ' ' + ss
	if mt == 'message':
		return '%s %s %s NIL)' % (s, get_param(m, 'content-md5'), cdls(m, True))
	return '%s %s %s NIL)' % (s, get_param(m, 'content-md5'), cdls(m, False))

def get_bodystructure(m):
	#print '(' + newbds(m)
	return get_bodystructure_old(m)
	#return '(' + newbds(m)

def structure(msg, resp = '' , level = 0):
	resp += ' ' * (level) + msg.get_content_type() + '\n'
	if msg.is_multipart():
		for subpart in msg.get_payload():
			resp = structure(subpart, resp, level + 1)
	if level == 0:
		return resp[:-1]
	return resp

def new_file(d, f, s):
	fd = None
	fn = os.path.join(d, f)
	if os.path.exists(fn):
		i = 1
		while os.path.exists(fn + '.' + str(i)):
			i += 1
		fp = open(fn + '.' + str(i), "wb")
	else:
		fp = open(fn, "wb")
	fp.write(s)
	fp.close()

def save_message(msg, directory = '/tmp', first = True):
	#directory = os.path.join(directory, str(time()))
	if first:
		try:
			#rmtree(directory)
			os.makedirs(directory)
		except Exception:
			pass

	if hasattr(msg, 'header') and msg.header is not None:
		new_file(directory, 'header.txt', msg.header)

	if hasattr(msg, 'raw') and msg.raw is not None:
		new_file(directory, 'raw.txt', msg.raw)

	if msg.is_multipart():
		for subpart in msg.get_payload():
			save_message(subpart, directory, False)
	else:
		payload = msg.get_payload(decode = 1)		
		if len(payload) == 0:
			return
		file_name = msg.get_filename()
		if not file_name:
			import mimetypes
			ext = mimetypes.guess_extension(msg.get_content_type())
			if not ext or ext == '.ksh':
				if msg.get_content_type().lower() == 'text/plain':
					ext = '.txt'
				else:
					t = msg.get_content_type().split('/')
					if len(t) == 2:
						ext = '.' + t[1]
					else:
						#generic extension
						ext = ".bin"
			file_name = str(time()) + ext
		new_file(directory, file_name, payload)

def message_from_string(s, try_add_boudary = False):
	s = str(s)
	raw = s
	if try_add_boudary:
		l = 0
		if s[2] == CRLF:
			l = 2
		if s[0] == '\n':
			l = 1
		if s[l:l + 2] == '--':
			boundary = s[l + 2:s.find('\n', l + 2)]
			s = 'MIME-Version: 1.0%sContent-Type: Multipart/Mixed; Boundary="%s"%sContent-Transfer-Encoding: Binary%s%s%s' % (CRLF, boundary, CRLF, CRLF, CRLF, s)
	h = s.find(2 * CRLF)
	if h == -1: h = s.find('\n\n')
	if h != -1:
		h = s[:h + 1]
	else:
		h = None

	b1 = 0
	while True:
		b1 = s.find('Boundary="', b1)
		if b1 == -1:
			break
		b1 += 10
		b2 = s.find('"', b1)
		b = s[b1:b2]
		bp = b.replace('>', 'o')
		if bp != b:
			s = s.replace(b, bp)
	m = email.message_from_string(s)
	m.header = h
	m.raw = raw
	return m

class GMessage(Logged):
	headers = {}
	bin_skipped_bcc = None
	b64_skipped_bcc = None
	def __init__(self, file, headers, text, rfc822file, rfc822, binary_data, contain):
		if contain is not None:
			from email.mime.multipart import MIMEMultipart
			self.m = MIMEMultipart()
			for c in contain:
				a = config.get_obj(c)
				if a is None:
					self.log_error('No attachment %s' % c)
				else:
					self.m.attach(a.m)	
		else:
			from email.message import Message
			self.m = Message()
			self.m.add_header('MIME-Version', '1.0')
		if file is not None:
			try:
				self.m.set_payload(open(file, 'r').read())
			except Exception, e:
				self.log_error(str(e))
			else:
				self.m.add_header('Content-Transfer-Encoding', '8bit')
				self.headers['Content-Transfer-Encoding'.lower()] = '8bit'
				self.m.add_header('Content-Disposition', 'inline; filename=%s' % os.path.basename(file)) 
				self.headers['Content-Disposition'.lower()] = 'inline; filename=%s' % os.path.basename(file)
		elif text is not None:
			self.m.set_payload(text)
		elif rfc822file is not None:
			from mime import message_from_string
			from utils import read_data
			rfc822 = read_data(rfc822file)
		elif binary_data is not None:
			self.m.set_payload(binary_data)

		if rfc822 is not None:
			from mime import message_from_string
			self.m = message_from_string(rfc822)

		# set headers
		for h in headers:
			if self.m.get(h):
				self.m.replace_header(h, headers[h])
			else:
				self.m.add_header(h, headers[h])
			self.headers[h.lower()] = headers[h]

		# set unparsed string for binary and base64
		convert_to_binary(self.m)
		self.bin = self.m.as_string()
		convert_to_b64(self.m)
		self.b64 = self.m.as_string()

	def get_param(self, param, format = True):
		param = param.lower()
		if param in self.headers:
			if format:
				return '"%s"' % self.headers[param]
			return self.headers[param]
		return 'NIL'

	def as_string(self, binary = False, skip_bcc = False):
		if skip_bcc and 'bcc' in self.headers:
			if binary:
				if self.bin_skipped_bcc is None:
					self.m.__delitem__('bcc')
					convert_to_binary(self.m)
					self.bin_skipped_bcc = self.m.as_string()
					self.m.add_header('bcc', self.get_param('bcc'))
				return self.bin_skipped_bcc
			else:
				if self.b64_skipped_bcc is None:
					self.m.__delitem__('bcc')
					convert_to_b64(self.m)
					self.b64_skipped_bcc = self.m.as_string()
					self.m.add_header('bcc', self.get_param('bcc'))
				return self.b64_skipped_bcc
		if binary: return self.bin
		return self.b64

